package org.lionsoul.websnail.store;

import org.fusesource.leveldbjni.JniDBFactory;
import org.iq80.leveldb.*;

import java.io.File;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Map.Entry;

/**
 * Persistent visited implementation base on levelDB
 *
 * @author chenxin<chenxin619315 @ gmail.com>
 */
public class LevelDBVisited implements Visited {
    private static final JniDBFactory factory = JniDBFactory.factory;
    private static final byte[] vBytes = new byte[]{1};
    private static MessageDigest md5Digest = null;

    static {
        try {
            md5Digest = MessageDigest.getInstance("md5");
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
    }

    /**
     * level DB instance
     */
    private DB db;
    private final ReadOptions rdOpt;
    private final WriteOptions wtOpt;

    /**
     * Serials of construct method,
     * so you can define the DB engine yourself if you know level DB well
     */
    public LevelDBVisited(String dbFile) throws IOException {
        this(dbFile, getDefaultOptions());
    }

    public LevelDBVisited(String dbFile, Options options) throws IOException {
        this(dbFile, options, new ReadOptions(), new WriteOptions());
    }

    public LevelDBVisited(String dbFile, Options options,
                          ReadOptions rdOpt, WriteOptions wtOpt) throws IOException {
        File dbFile1;
        if (dbFile.endsWith(File.pathSeparator)) {
            dbFile1 = new File(dbFile + "visited");
        } else {
            dbFile1 = new File(dbFile + File.separator + "visited");
        }
        if (!dbFile1.exists()) {
            dbFile1.mkdirs();
        }

        if (null == options) {
            options = getDefaultOptions();
        }

        this.rdOpt = rdOpt;
        this.wtOpt = wtOpt;
        this.db = factory.open(dbFile1, options);
    }

    /**
     * default options settings
     *
     * @return Options
     */
    private static Options getDefaultOptions() {
        Options options = new Options();
        options.createIfMissing(true);
        options.errorIfExists(false);
        options.compressionType(CompressionType.SNAPPY);
        options.cacheSize(100 * 1048576);    //100MB cache

        return options;
    }

    /**
     * pre-process for the key string
     *
     * @return byte[]
     */
    private static byte[] key(String key) {
        return md5Digest.digest(JniDBFactory.bytes(key));
    }


    @Override
    public synchronized boolean add(String url) {
        db.put(key(url), vBytes, wtOpt);
        return true;
    }

    @Override
    public synchronized boolean add(String[] urls) {
        WriteBatch batch = db.createWriteBatch();
        for (String url : urls) {
            batch.put(key(url), vBytes);
        }

        db.write(batch, wtOpt);
        return true;
    }

    @Override
    public synchronized boolean remove(String url) {
        db.delete(key(url), wtOpt);
        return true;
    }

    @Override
    public synchronized void clear() {
        /*
         * delete all the records
         * well, a better way should be replaced for this
         */
        DBIterator it = db.iterator(rdOpt);
        for (it.seekToFirst(); it.hasNext(); ) {
            Entry<byte[], byte[]> e = it.next();
            db.delete(e.getKey(), wtOpt);
        }

        try {
            it.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public synchronized boolean contains(String url) {
        return db.get(key(url), rdOpt) != null;
    }

    @Override
    public synchronized int size() {
        return 0;
    }

    @Override
    public synchronized void close() {
        try {
            db.close();
            db = null;
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}
